﻿using System;
using System.Drawing;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.IO;

using Color = Microsoft.Xna.Framework.Color;
using Vector4 = Microsoft.Xna.Framework.Vector4;
using SysColor = System.Drawing.Color;

namespace MaterialEditor
{
    public partial class NodeValue
    {
        #region Multiply functions

        private static Texture2D MultColorAndTexture(Texture2D tex, Color color)
        {
            Color[] data = new Color[tex.Width * tex.Height];
            tex.GetData(data);

            Vector4 v1 = color.ToVector4();
            for (int i = 0; i < data.Length; i++)
            {
                Vector4 v2 = data[i].ToVector4();

                data[i] = new Color(v1 * v2);
            }

            Texture2D outval = new Texture2D(Graphics.Instance.device, tex.Width, tex.Height);
            outval.SetData(data);

            return outval;
        }

        private static float MultFloatAndFloat(float a, float b)
        {
            return a * b;
        }

        private static Color MultColorAndFloat(float f, Color c)
        {
            Vector4 v = c.ToVector4();
            v *= f;

            return new Color(v);
        }

        private static Texture2D MultFloatAndTexture(float f, Texture2D b)
        {
            Color[] data = new Color[b.Width * b.Height];
            b.GetData(data);

            for (int i = 0; i < data.Length; i++)
            {
                data[i] = new Color(data[i].ToVector4() * f);
            }

            Texture2D outval = new Texture2D(Graphics.Instance.device, b.Width, b.Height);
            outval.SetData(data);

            return outval;
        }

        private static TextureContainer MultTextureAndTexture(TextureContainer tex1, TextureContainer tex2)
        {
            TextureContainer outval = new TextureContainer(tex1.texture.Material);
            outval.texture.effects += tex1.texture.effects;
            outval.texture.effects += tex2.texture.effects;

            MultTexAndTex m = new MultTexAndTex();
            m.otherMat = tex2.texture.Material;
            outval.texture.effects += m;

            return outval;

        }

        static Color MultColorAndColor(Color cA, Color cB)
        {
            Vector4 vA = cA.ToVector4();
            Vector4 vB = cB.ToVector4();

            return new Color(vA * vB);

        }

        public static NodeValue operator *(NodeValue a, NodeValue b)
        {
            if (a == null && b == null)
                return null;
            if (a == null)
                return b;
            if (b == null)
                return a;
            if (a.type == NodeValueType.ValueTexture)
            {
                if (b.type == NodeValueType.ValueColor)
                    return new NodeValue(MultColorAndTexture(a.AsTexture2D(), (Color)b.Value));
                if (b.type == NodeValueType.ValueTexture)
                    return new NodeValue(MultTextureAndTexture(a.AsTextureContainer(), b.AsTextureContainer()));
                if (b.type == NodeValueType.ValueFloat)
                    throw new Exception("Unable to multiply float and texture");
                if (b.type == NodeValueType.ValueVector2)
                    throw new InvalidOperationException();
                if (b.type == NodeValueType.ValueVector3)
                    return new NodeValue(MultColorAndTexture(a.AsTexture2D(), new Color((Vector3)b.Value)));
            }
            if (a.type == NodeValueType.ValueFloat)
            {
                if (b.type == NodeValueType.ValueFloat)
                    return new NodeValue((float)(a.Value) * (float)(b.Value));
                if (b.type == NodeValueType.ValueColor)
                    return new NodeValue(MultColorAndFloat((float)a.Value, (Color)b.Value));
                if (b.type == NodeValueType.ValueTexture)
                    return new NodeValue(MultFloatAndTexture((float)a.Value, b.Value as Texture2D));
                if (b.type == NodeValueType.ValueVector2)
                    return new NodeValue((float)a.Value * (Vector2)b.Value);
                if (b.type == NodeValueType.ValueVector3)
                    return new NodeValue((float)a.Value * (Vector3)b.Value);
            }
            if (a.type == NodeValueType.ValueColor)
            {
                if (b.type == NodeValueType.ValueColor)
                    return new NodeValue(MultColorAndColor((Color)a.Value, (Color)b.Value));
                if (b.type == NodeValueType.ValueFloat)
                    return new NodeValue(MultColorAndFloat((float)b.Value, (Color)b.Value));
                if (b.type == NodeValueType.ValueTexture)
                    return new NodeValue(MultColorAndTexture(b.AsTexture2D(), (Color)a.Value));
                if (b.type == NodeValueType.ValueVector2)
                    throw new InvalidOperationException();
                if (b.type == NodeValueType.ValueVector3)
                    return new NodeValue(MultColorAndColor((Color)a.Value, new Color((Vector3)b.Value)));
            }
            if (a.type == NodeValueType.ValueVector2)
            {
                if (b.type == NodeValueType.ValueVector2)
                    return new NodeValue((Vector2)a.Value * (Vector2)b.Value);
                if (b.type == NodeValueType.ValueVector3)
                    throw new InvalidOperationException();
                if (b.type == NodeValueType.ValueFloat)
                    return new NodeValue((Vector2)a.Value * (float)b.Value);
                if (b.type == NodeValueType.ValueTexture)
                    throw new InvalidOperationException();
                if (b.type == NodeValueType.ValueColor)
                    throw new InvalidOperationException();
            }
            if (a.type == NodeValueType.ValueVector3)
            {
                if (b.type == NodeValueType.ValueVector3)
                    return new NodeValue((Vector3)a.Value * (Vector3)b.Value);
                if (b.type == NodeValueType.ValueVector2)
                    throw new InvalidOperationException();
                if (b.type == NodeValueType.ValueColor)
                    return new NodeValue((Vector3)a.Value * ((Color)b.Value).ToVector3());
                if (b.type == NodeValueType.ValueFloat)
                    return new NodeValue((Vector3)a.Value * (float)b.Value);
                if (b.type == NodeValueType.ValueTexture)
                    return new NodeValue(MultColorAndTexture(b.AsTexture2D(), (Color)b.Value));
            }

            return null;
        }
        #endregion
        #region Add functions

        private static Color addColorAndColor(Color a, Color b)
        {
            Vector4 vA = a.ToVector4();
            Vector4 vB = b.ToVector4();

            return new Color(vA + vB);
        }

        private static TextureContainer addTextureAndTexture(TextureContainer a, TextureContainer b)
        {
            TextureContainer outval = new TextureContainer(a.texture.Material);

            AddTexAndTex effect = new AddTexAndTex();
            effect.otherMat = b.texture.Material;
            outval.texture.effects += a.texture.effects;
            outval.texture.effects += b.texture.effects;
            outval.texture.effects += effect;

            return outval;
        }

        private static Texture2D addColorAndTexture(Texture2D b, Color c)
        {
            Color[] data = new Color[b.Width * b.Height];
            b.GetData(data);

            Vector4 v1 = c.ToVector4();
            for (int i = 0; i < data.Length; i++)
            {
                Vector4 v2 = data[i].ToVector4();

                data[i] = new Color(v1 + v2);
            }

            Texture2D outval = new Texture2D(Graphics.Instance.device, b.Width, b.Height);
            outval.SetData(data);

            return outval;
        }

        static Color AddColorAndVector3(Color c, Vector3 a)
        {
            return new Color(c.ToVector3() + a);
        }

        
        public static NodeValue operator +(NodeValue a, NodeValue b)
        {
            if (a == null && b == null)
                return null;
            if (a.type == NodeValueType.ValueColor)
            {
                if (b.type == NodeValueType.ValueColor)
                    return new NodeValue(addColorAndColor((Color)a.Value, (Color)b.Value));
                if (b.type == NodeValueType.ValueTexture)
                    return new NodeValue(addColorAndTexture(b.AsTexture2D(), (Color)a.Value));
                if (b.type == NodeValueType.ValueFloat)
                    throw new InvalidOperationException("Cannot add a color and a number");
                if (b.type == NodeValueType.ValueVector3)
                    return new NodeValue(AddColorAndVector3((Color)a.Value, (Vector3)b.Value));
                if (b.type == NodeValueType.ValueVector2)
                    throw new InvalidOperationException();
            }
            if (a.type == NodeValueType.ValueFloat)
            {
                if (b.type == NodeValueType.ValueFloat)
                    return new NodeValue(((float)a.Value) + ((float)b.Value));
                if (b.type == NodeValueType.ValueColor)
                    throw new InvalidOperationException("Cannot add a color and a number");
                if (b.type == NodeValueType.ValueTexture)
                    throw new InvalidOperationException("Cannot add a number and a texture");
                if (b.type == NodeValueType.ValueVector2)
                    throw new InvalidOperationException();
                if (b.type == NodeValueType.ValueVector3)
                    throw new InvalidOperationException();
            }
            if (a.type == NodeValueType.ValueTexture)
            {
                if (b.type == NodeValueType.ValueColor)
                    return new NodeValue(addColorAndTexture(a.AsTexture2D(), (Color)b.Value));
                if (b.type == NodeValueType.ValueTexture)
                    return new NodeValue(addTextureAndTexture(a.AsTextureContainer(), b.AsTextureContainer()));
                if (b.type == NodeValueType.ValueFloat)
                    throw new InvalidOperationException("Cannot add a number and a texture");
                if (b.type == NodeValueType.ValueVector2)
                    throw new InvalidOperationException();
                if (b.type == NodeValueType.ValueVector3)
                    return new NodeValue(addColorAndTexture(a.AsTexture2D(), new Color((Vector3)b.Value)));
            }
            if (a.type == NodeValueType.ValueVector2)
            {
                if (b.type == NodeValueType.ValueVector2)
                    return new NodeValue((Vector2)a.Value + (Vector2)b.Value);
                if (b.type == NodeValueType.ValueVector3)
                    return new NodeValue(new Vector3((Vector2)a.Value, 0.0f) + (Vector3)b.Value);
                if (b.type == NodeValueType.ValueTexture)
                    throw new InvalidOperationException();
                if (b.type == NodeValueType.ValueFloat)
                    throw new InvalidOperationException();
                if (b.type == NodeValueType.ValueColor)
                    throw new InvalidOperationException();
            }
            if (a.type == NodeValueType.ValueVector3)
            {
                if (b.type == NodeValueType.ValueVector3)
                    return new NodeValue((Vector3)a.Value + (Vector3)b.Value);
                if (b.type == NodeValueType.ValueVector2)
                {
                    Vector2 b_val = (Vector2)b.Value;
                    return new NodeValue((Vector3)a.Value + new Vector3(b_val.X, b_val.Y, 0.0f));
                }
                if (b.type == NodeValueType.ValueFloat)
                    throw new InvalidOperationException();
                if (b.type == NodeValueType.ValueColor)
                    return new NodeValue(AddColorAndVector3((Color)b.Value, (Vector3)a.Value));
                if (b.type == NodeValueType.ValueTexture)
                    return new NodeValue(addColorAndTexture(b.AsTexture2D(), new Color((Vector3)a.Value)));
            }
            return null;
        }

        #endregion
        #region Subtract Functions

        static Texture2D subtractTextureAndTexture(Texture2D a, Texture2D b)
        {
            if (a.Width != b.Width ||
                a.Height != b.Height)
                throw new InvalidOperationException("Cannot subtract two textures of unequal size!");
            Color[] data_a = new Color[a.Width * a.Height];
            Color[] data_b = new Color[b.Width * b.Height];

            a.GetData(data_a);
            b.GetData(data_b);

            for (int i = 0; i < data_a.Length; i++)
            {
                Vector4 v1 = data_a[i].ToVector4();
                Vector4 v2 = data_b[i].ToVector4();

                data_a[i] = new Color(v1 - v2);
            }

            Texture2D outval = new Texture2D(Graphics.Instance.device, a.Width, a.Height);
            outval.SetData(data_a);

            return outval;
        }

        static Texture2D subtractTextureAndFloat(Texture2D t, float f)
        {
            Color[] data = new Color[t.Width * t.Height];
            t.GetData(data);

            for (int i = 0; i < data.Length; i++)
            {
                Vector4 v = data[i].ToVector4();

                data[i] = new Color(new Vector4(v.X - f, v.Y - f, v.Z - f, v.W - f));
            }

            Texture2D outval = new Texture2D(Graphics.Instance.device, t.Width, t.Height);
            outval.SetData(data);

            return outval;
        }

        static Texture2D subtractTextureAndColor(Texture2D t, Color c)
        {
            Color[] data = new Color[t.Width * t.Height];
            t.GetData(data);

            Vector4 v1 = c.ToVector4();
            for (int i = 0; i < data.Length; i++)
            {
                Vector4 v2 = data[i].ToVector4();

                data[i] = new Color(v1 - v2);
            }

            Texture2D outval = new Texture2D(Graphics.Instance.device, t.Width, t.Height);
            outval.SetData(data);

            return outval;
        }

        public static NodeValue operator -(NodeValue a, NodeValue b)
        {
            if (a.type == NodeValueType.ValueTexture)
            {
                if (b.type == NodeValueType.ValueTexture)
                    return new NodeValue(subtractTextureAndTexture(a.AsTexture2D(), b.AsTexture2D()));
                if (b.type == NodeValueType.ValueFloat)
                    return new NodeValue(subtractTextureAndFloat(a.AsTexture2D(), (float)b.Value));
                if (b.type == NodeValueType.ValueColor)
                    return new NodeValue(subtractTextureAndColor(a.AsTexture2D(), (Color)b.Value));
                if (b.type == NodeValueType.ValueVector2)
                    throw new InvalidOperationException();
                if (b.type == NodeValueType.ValueVector3)
                    return new NodeValue(subtractTextureAndColor(a.AsTexture2D(), new Color((Vector3)b.Value)));
            }
            if (a.type == NodeValueType.ValueColor)
            {
                if (b.type == NodeValueType.ValueColor)
                    return new NodeValue(new Color(((Color)a.Value).ToVector3() - ((Color)b.Value).ToVector3()));
                if (b.type == NodeValueType.ValueFloat)
                    throw new InvalidOperationException();
                if (b.type == NodeValueType.ValueTexture)
                    subtractTextureAndColor(b.AsTexture2D(), (Color)a.Value);
                if (b.type == NodeValueType.ValueVector2)
                    throw new InvalidOperationException();
                if (b.type == NodeValueType.ValueVector3)
                    return new NodeValue(((Color)a.Value).ToVector3() - (Vector3)b.Value);
            }
            if (a.type == NodeValueType.ValueFloat)
            {
                if (b.type == NodeValueType.ValueFloat)
                    return new NodeValue((float)a.Value - (float)b.Value);
                if (b.type == NodeValueType.ValueColor)
                    throw new InvalidOperationException();
                if (b.type == NodeValueType.ValueTexture)
                    return new NodeValue(subtractTextureAndFloat(b.AsTexture2D(), (float)a.Value));
                if (b.type == NodeValueType.ValueVector2)
                    throw new InvalidOperationException();
                if (b.type == NodeValueType.ValueVector3)
                    throw new InvalidOperationException();
            }
            if (a.type == NodeValueType.ValueVector2)
            {
                if (b.type == NodeValueType.ValueVector2)
                    return new NodeValue((Vector2)a.Value - (Vector2)b.Value);
                if (b.type == NodeValueType.ValueVector3)
                    throw new InvalidOperationException();
                if (b.type == NodeValueType.ValueColor)
                    throw new InvalidOperationException();
                if (b.type == NodeValueType.ValueFloat)
                    throw new InvalidOperationException();
                if (b.type == NodeValueType.ValueTexture)
                    throw new InvalidOperationException();
            }
            if (a.type == NodeValueType.ValueVector3)
            {
                if (b.type == NodeValueType.ValueVector3)
                    return new NodeValue((Vector3)a.Value - (Vector3)b.Value);
                if (b.type == NodeValueType.ValueVector2)
                    throw new InvalidOperationException();
                if (b.type == NodeValueType.ValueTexture)
                    return new NodeValue(subtractTextureAndColor(b.AsTexture2D(), new Color((Vector3)a.Value)));
                if (b.type == NodeValueType.ValueFloat)
                    throw new InvalidOperationException();
                if (b.type == NodeValueType.ValueColor)
                    return new NodeValue((Vector3)a.Value - ((Color)b.Value).ToVector3());
            }

            return null;
        }
        #endregion
        #region

        static Texture2D lerpTextures(Texture2D a, Texture2D b, float lerp)
        {
            if (a.Width != b.Width ||
                a.Height != b.Height)
                throw new InvalidOperationException();

            Color[] a_data = new Color[a.Width * a.Height];
            Color[] b_data = new Color[b.Width * b.Height];

            a.GetData(a_data);
            b.GetData(b_data);

            for (int i = 0; i < a_data.Length; i++)
                a_data[i] = Color.Lerp(a_data[i], b_data[i], lerp);

            Texture2D outval = new Texture2D(Graphics.Instance.device, a.Width, a.Height);
            outval.SetData(a_data);

            return outval;
        }

        public static NodeValue Lerp(NodeValue a, NodeValue b, float weight)
        {
            if (a.type != b.type)
                throw new InvalidOperationException();

            if (a.type == NodeValueType.ValueColor)
                return new NodeValue(Color.Lerp((Color)a.Value, (Color)b.Value, weight));
            if (a.type == NodeValueType.ValueFloat)
                return new NodeValue(MathHelper.Lerp((float)a.Value, (float)b.Value, weight));
            if (a.type == NodeValueType.ValueTexture)
                return new NodeValue(lerpTextures(a.Value as Texture2D, b.Value as Texture2D, weight));
            if (a.type == NodeValueType.ValueVector2)
                return new NodeValue(Vector2.Lerp((Vector2)a.Value, (Vector2)b.Value, weight));
            if (a.type == NodeValueType.ValueVector3)
                return new NodeValue(Vector3.Lerp((Vector3)a.Value, (Vector3)b.Value, weight));

            return null;
        }


        #endregion
    }
}